<template>
	<view class="w-process" v-if="!data.loading && data.processTasks.length > 0">
		<view class="w-process-line"></view>
		<view class="w-process-render" v-for="node in data.processTasks" :key="node.id">
			<process-node-render :key="update" :ref="node.id" class="w-node-render" :task="node" @addOrg="addOrg" @delOrg="delOrg" />
		</view>
		<org-picker ref="orgPicker" :type="data.selectedNode.type || 'user'"
			:multiple="data.selectedNode.multiple || false" :selected="data.selectedNode.users" @ok="doAddOrg" />
	</view>
</template>

<script setup>
	import { nextTick, reactive, computed, onBeforeMount, getCurrentInstance, watch, ref } from 'vue';
	import { $deepCopy } from '@/utils/tool.js'
	import ProcessNodeRender from "./ProcessNodeRender.vue";
	import { forEachNode } from '@/utils/ProcessUtil.js'
	import processApi from '@/api/process'
	import OrgPicker from "@/components/OrgPicker.vue";
	import { getGroupModels } from "@/api/model";
	import { debounce } from '@/utils/tool.js'
	import { useI18n } from 'vue-i18n';

	const { t } = useI18n();

	const instance = getCurrentInstance()
	const update = ref(0)

	//小程序端不支持jsx，真是艹了，这块要重写😂
	const props = defineProps({
		processDefId: String,
		process: {
			type: Object,
			default: () => {
				return {}
			}
		},
		formData: {
			type: Object,
			default: () => {
				return {}
			}
		},
		modelValue: {
			type: Object,
			default: () => {
				return {}
			}
		},
		deptId: {
			type: String,
			default: null
		},
	})

	const _value = computed({
		get() {
			return props.modelValue
		},
		set(val) {
			emits('update:modelValue', val)
		}
	})

	defineExpose({ validate })
	const emits = defineEmits(['update:modelValue', 'render-ok'])

	const loginUser = uni.getStorageSync('loginUser')
	loginUser.value = { id: loginUser.id, name: loginUser.userName, avatar: loginUser.avatar }

	const orgPicker = ref()
	const data = reactive({
		selectUserNodes: new Set(),
		loading: false,
		selectedNode: {},
		reverse: false,
		userCatch: {},
		oldFormData: {},
		models: null,
		processTasks: [],
		conditionFormItem: new Set(),
		branchNodeMap: new Map(),
		loadingReqs: [],
		calls: []
	})

	onBeforeMount(() => loadProcessRender())

	//流程渲染去抖动
	const processRenderDebounce = debounce(() => loadProcessRender(), 500)

	async function loadProcessRender() {
		console.log('渲染流程')
		data.loading = true
		data.processTasks.length = 0
		data.selectUserNodes.clear()
		data.loadingReqs.length = 0
		//TODO 从这里可以使用去抖动函数 this.$debounce
		loadProcess(props.process, data.processTasks)
		data.processTasks.push({
			title: t('webAuto.end'),
			name: 'END',
			icon: 'checkbox-filled',
			enableEdit: false
		})
		if (data.loadingReqs.length > 0) {
			Promise.all(data.loadingReqs).then(() => {
				data.loading = false
				setTimeout(() => {
					update.value++
				}, 300)
				emits('render-ok')
			}).catch(() => data.loading = false)
		} else {
			emits('render-ok')
			data.loading = false
		}
	}

	function loadProcess(processNode, processTasks, bnode, bid) {
		forEachNode(processNode, node => {
			if (bnode) { //如果是分支内子节点
				data.branchNodeMap.set(node.id, { node: bnode, id: bid })
			}
			switch (node.type) {
				case 'ROOT':
					processTasks.push({
						id: node.id,
						title: t(node.name),
						name: t('webAuto.promoter'),
						desc: `${loginUser.value.name} ${t('webAuto.ProcessInitiated')}`,
						icon: 'contact-filled',
						enableEdit: false,
						users: [loginUser.value]
					});
					break;
				case 'APPROVAL':
					processTasks.push(getApprovalNode(node))
					break;
				case 'TASK':
					processTasks.push(getApprovalNode(node, false))
					break;
				case 'SUBPROC':
					processTasks.push(getSubProcNode(node))
					break;
				case 'CC':
					processTasks.push(getCcNode(node))
					break;
				case 'CONDITIONS': //条件节点选一项
					processTasks.push(getConditionNode(node, bnode, bid))
					loadProcess(node.children, processTasks)
					return true
				case 'INCLUSIVES': //包容分支会执行所有符合条件的分支
					processTasks.push(getInclusiveNode(node, bnode, bid))
					loadProcess(node.children, processTasks)
					return true
				case 'CONCURRENTS': //并行分支无条件执行所有分支
					processTasks.push(getConcurrentNode(node, bnode, bid))
					loadProcess(node.children, processTasks)
					return true
			}
		})
	}

	function getSubProcNode(node) {
		let user = {}
		//提取发起人
		switch (node.props.staterUser.type) {
			case "ROOT":
				user = loginUser.value;
				break;
			case "FORM":
				const fd = props.formData[node.props.staterUser.value]
				user = Array.isArray(fd) && fd.length > 0 ? fd[0] : { name: t('module.selectPeoples') };
				break;
			case "SELECT":
				user = node.props.staterUser.value || {};
				break;
		}
		const procNode = {
			id: node.id,
			title: `${t(node.name)} [${t('webAuto.by')}${user ? user.name : '?'}${t('webAuto.initiate')}]`,
			name: t('webAuto.subProcess'),
			desc: t('webAuto.ProcessOfSendingScrewdriver'),
			icon: 'more-filled',
			enableEdit: false,
			users: [user]
		}
		getSubModel(() => {
			procNode.desc = `${t('webAuto.CallSubprocess')} [${data.models[node.props.subProcCode]}]`
		})
		return procNode
	}
	
	function resolveDeptNode(depts, deptProp, result, isApproval){
      let deptIds = (depts || []).map(d => d.id)
      switch (deptProp?.type){
        case 'ALL':
          result.desc = `${t('webAuto.departmentEveryone')}${isApproval?t('webAuto.ExamineApprove'):t('webAuto.Handle')}`;
          result.users = depts
          break;
        case 'ROLE':
          result.desc = `${t('webAuto.departmentRole')}[${(deptProp.roles || []).map(r => r.name)}]${isApproval?t('webAuto.ExamineApprove'):t('webAuto.Handle')}`
          data.loadingReqs.push(
              processApi.getDeptUsersByRoles((deptProp.roles || []).map(r => r.id), deptIds).then(res => {
                result.users = res.data
              }))
          break;
        default:
          data.loadingReqs.push(
              processApi.getLeaderByDepts(deptIds).then(res => {
                result.users = res.data
              }))
          result.desc = `${t('webAuto.Manager')}${isApproval?t('webAuto.ExamineApprove'):t('webAuto.Handle')}`;
          break;
      }
    }

	function getApprovalNode(node, isApproval = true) {
		let result = {
			id: node.id,
			title: t(node.name),
			name: isApproval ? t('webAuto.Approver') :t('webAuto.transactors'),
			icon: isApproval ? 'person-filled' : 'calendar-filled',
			enableEdit: false,
			multiple: false,
			mode: node.props.mode,
			users: [],
			desc: ''
		}
		let loadCatch = true
		switch (node.props.assignedType) {
			case 'ASSIGN_USER':
				result.users = $deepCopy(node.props.assignedUser)
				result.desc = isApproval ? t('webAuto.DesignatedApprover')  : t('webAuto.DesignatedHandler')
				break
			case 'ASSIGN_LEADER':
				resolveDeptNode(node.props.assignedDept, node.props.deptProp, result, isApproval)
				break
			case 'SELF':
				result.users = [loginUser.value]
				result.desc = `${t('webAuto.InitiatorThemselves')}${isApproval?t('webAuto.ExamineApprove'):t('webAuto.Handle')}`
				break
			case 'SELF_SELECT':
				result.enableEdit = true
				data.selectUserNodes.add(node.id)
				result.multiple = node.props.selfSelect.multiple || false
				result.desc = isApproval ? t('webAuto.SelfSelectedApprover') : t('webAuto.SelfSelectedHandler')
				break
			case 'LEADER_TOP':
				result.desc = `${t('webAuto.ContinuousSupervisor')}${isApproval?t('webAuto.ExamineApprove'):t('webAuto.Handle')}`
				const leaderTop = node.props.leaderTop
				data.loadingReqs.push(
					processApi.getUserLeaders(
						'TOP' === leaderTop.endCondition ? 0 : leaderTop.endLevel,
						props.deptId, leaderTop.skipEmpty).then(res => {
						result.users = res.data
					}))
				break
			case 'LEADER':
				result.desc = node.props.leader.level === 1 ?
					`${t('webAuto.ImmediateSupervisor')}${isApproval?t('webAuto.ExamineApprove'):t('webAuto.Handle')}` :
					`${node.props.leader.level}${t('webAuto.LevelSupervisor')}${isApproval?t('webAuto.ExamineApprove'):t('webAuto.Handle')}`
				data.loadingReqs.push(
					processApi.getUserLeader(
						node.props.leader.level,
						props.deptId,
						node.props.leader.skipEmpty).then(res => {
						result.users = res.data ? [res.data] : [],
						console.log(6666)
					}))
				break
			case 'ROLE':
				result.desc = `${t('webAuto.ByRole')}[${(node.props.role || []).map(r => r.name)}]${isApproval?t('webAuto.ExamineApprove'):t('webAuto.Handle')}`
				data.loadingReqs.push(
					processApi.getUsersByRoles((node.props.role || []).map(r => r.id)).then(res => {
						result.users = res.data
					}))
				break
			case 'FORM_USER':
				loadCatch = false
				result.desc = `${t('webAuto.PersonnelForm')}${isApproval?t('webAuto.ExamineApprove'):t('webAuto.Handle')}`
				data.conditionFormItem.add(node.props.formUser)
				result.users = props.formData[node.props.formUser] || []
				break
			case 'FORM_DEPT':
				loadCatch = false
				result.desc = `${t('webAuto.SupervisedDepartment')}${isApproval?t('webAuto.ExamineApprove'):t('webAuto.Handle')}`
				data.conditionFormItem.add(node.props.formDept)
				data.loadingReqs.push(
					processApi.getLeaderByDepts((props.formData[node.props.formDept] || []).map(d => d.id)).then(
						res => {
							result.users = res.data
						}))
				break
			case 'OTHER_SELECT':
				result.desc = `${t('webAuto.OtherNodes')}${isApproval?t('webAuto.ExamineApprove'):t('webAuto.Handle')}`
				break
			case 'REFUSE':
				result.desc = t('webAuto.rejectProcess')
				break
		}
		if (data.userCatch[node.id] && data.userCatch[node.id].length > 0) {
			result.users = data.userCatch[node.id]
		}
		if (loadCatch) {
			data.userCatch[node.id] = result.users
		}
		return result
	}

	function getCcNode(node) {
		let result = {
			id: node.id,
			title: t(node.name),
			icon: 'paperplane-filled',
			name: t('webAuto.Ccto'),
			enableEdit: node.props.shouldAdd,
			type: 'org',
			multiple: true,
			desc: node.props.shouldAdd ? t('webAuto.Cancc') : t('webAuto.processWillCopied'),
			users: $deepCopy(node.props.assignedUser)
		}
		if (data.userCatch[node.id] && data.userCatch[node.id].length > 0) {
			result.users = data.userCatch[node.id]
		}
		data.userCatch[node.id] = result.users
		return result
	}

	function getInclusiveNode(node, pbnode, pbid) {
		let branchTasks = {
			id: node.id,
			title: t(node.name),
			name: t('webAuto.InclusiveBranch'),
			icon: 'tune-filled',
			enableEdit: false,
			active: node.branchs[0].id, //激活得分支
			options: [], //分支选项，渲染单选框
			desc: t('webAuto.AllBranchesEcuted'),
			branchs: {} //分支数据，不包含分支节点，key=分支子节点id，value = [后续节点]
		}
		const req = processApi.getTrueConditions({
			processDfId: props.processDefId,
			conditionNodeId: node.id,
			multiple: true,
			context: { ...props.formData, deptId: props.deptId }
		}).then(rsp => {
			//拿到满足的条件
			const cds = new Set(rsp.data || [])
			for (let i = 0; i < node.branchs.length; i++) {
				const cdNode = node.branchs[i]
				cdNode.skip = !cds.has(cdNode.id)
				if (!cdNode.skip) {
					branchTasks.active = cdNode.id
				}
			}
			node.branchs.forEach(nd => {
				branchTasks.options.push({
					id: nd.id,
					title: t(nd.name),
					skip: nd.skip
				})
				branchTasks.branchs[nd.id] = []
				//设置下子级分支的父级分支节点
				data.branchNodeMap.set(nd.id, { node: pbnode, id: pbid })
				loadProcess(nd.children, branchTasks.branchs[nd.id], branchTasks, nd.id)
			})
		}).catch(err => {
			branchTasks.desc = `<span style="color:#CE5266;">条件解析异常，渲染失败😢<span>`
			//
		})
		data.loadingReqs.push(req)
		return branchTasks
	}

	function getConditionNode(node, pbnode, pbid) {
		let branchTasks = {
			id: node.id,
			title: t(node.name),
			name: t('webAuto.conditionalBranch'),
			icon: 'tune',
			enableEdit: false,
			active: node.branchs[0].id, //激活得分支
			options: [], //分支选项，渲染单选框
			desc: t('webAuto.ExecuteFirstBranch'),
			branchs: {} //分支数据，不包含分支节点，key=分支子节点id，value = [后续节点]
		}
		const req = processApi.getTrueConditions({
			processDfId: props.processDefId,
			conditionNodeId: node.id,
			multiple: false,
			context: { ...props.formData, deptId: props.deptId }
		}).then(rsp => {
			//拿到满足的条件
			const cds = new Set(rsp.data || [])
			for (let i = 0; i < node.branchs.length; i++) {
				const cdNode = node.branchs[i]
				cdNode.skip = !cds.has(cdNode.id)
				if (!cdNode.skip) {
					branchTasks.active = cdNode.id
				}
			}
			node.branchs.forEach(nd => {
				branchTasks.options.push({
					id: nd.id,
					title: t(nd.name),
					skip: nd.skip
				})
				branchTasks.branchs[nd.id] = []
				//设置下子级分支的父级分支节点
				data.branchNodeMap.set(nd.id, { node: pbnode, id: pbid })
				loadProcess(nd.children, branchTasks.branchs[nd.id], branchTasks, nd.id)
			})
		}).catch(err => {
			branchTasks.desc = `<span style="color:#CE5266;">条件解析异常，渲染失败😢<span>`
			//this.$err(err, "解析条件失败:")
		})
		data.loadingReqs.push(req)
		return branchTasks
	}

	function getConcurrentNode(node, pbnode, pbid) {
		let concurrentTasks = {
			id: node.id,
			title: t(node.name),
			name: t('webAuto.parallelBranch'),
			icon: 'settings-filled',
			enableEdit: false,
			active: node.branchs[0].id, //激活得分支
			options: [], //分支选项，渲染单选框
			desc: t('webAuto.Allbranches'),
			branchs: {} //分支数据，不包含分支节点，key=分支子节点id，value = [后续节点]
		}
		node.branchs.forEach(nd => {
			concurrentTasks.options.push({
				id: nd.id,
				title: t(nd.name),
				skip: false
			})
			concurrentTasks.branchs[nd.id] = []
			//设置下子级分支的父级分支节点
			data.branchNodeMap.set(nd.id, { node: pbnode, id: pbid })
			loadProcess(nd.children, concurrentTasks.branchs[nd.id], concurrentTasks, nd.id)
		})
		return concurrentTasks
	}

	function selected(users) {
		_value.value[data.selectedNode.id] = []
		data.selectedNode.users.length = 0
		users.forEach(u => {
			u.enableEdit = true
			data.selectedNode.users.push(u)
		})
		_value.value[data.selectedNode.id] = data.selectedNode.users
	}

	function getSubModel(call) {
		if (data.models) {
			call()
		} else {
			data.calls.push(call)
			if (data.calls.length === 1) {
				getGroupModels({}, true).then(rsp => {
					data.models = {}
					rsp.data.forEach(group => {
						group.items.forEach(v => data.models[v.procCode] = v.procName)
					})
					data.calls.forEach(callFun => callFun())
					data.calls.length = 0
				})
			}
		}
	}

	function addOrg(node) {
		data.selectedNode = node
		orgPicker.value.show()
	}

	function doAddOrg(orgs) {
		selected(orgs)
	}

	function delOrg(i, node) {
		node.users.splice(i, 1)
	}

	//执行校验流程步骤设置
	function validate(call) {
		//遍历自选审批人节点
		let isOk = true
		for (let nodeId of data.selectUserNodes) {
			if ((_value.value[nodeId] || []).length === 0) {
				//没设置审批人员
				isOk = false
				//遍历所有的分支，从底部向上搜索进行自动切换分支渲染路线
				let brNode = data.branchNodeMap.get(nodeId)
				while (brNode && brNode.id) {
					brNode.node.active = brNode.id
					brNode = data.branchNodeMap.get(brNode.id)
				}

				nextTick(() => {
					if (instance.refs[nodeId]) {
						instance.refs[nodeId][0].errorShark()
					}
				})
				break
			}
		}
		if (call) {
			call(isOk)
		}
	}

	watch(props.formData, async () => {
		//监听表单数据变化，对流程重渲染
		processRenderDebounce()
	})
</script>

<style lang="less" scoped>
	.w-process {
		position: relative;

		.w-process-line {
			width: 2px;
			top: 20px;
			height: calc(100% - 40px);
			background-color: #9e9e9e;
			position: absolute;
			left: 13px;
		}
	}

	.w-process-render {
		.w-node-render {
			padding: 32rpx 0;
		}
	}
</style>